/**
 * Layer to display surfaces using SDL.
 */

#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <visual.h>
#include <visual_ppm.h>
#include <coord.h>
#include <debug.h>
#include <utils.h>

static surface_t *screen = NULL;

static struct visual_ops ppm_layer_ops = {
  .quit    = visual_ppm_quit,
  .init    = visual_ppm_init,
  .update  = visual_ppm_update,
  .waitkey = visual_ppm_waitkey,
  .clear   = visual_ppm_clear,
  .fill    = visual_ppm_fill,
  .display = visual_ppm_display,
  .save    = visual_ppm_save,
};

#define DEFAULT_FILENAME "output"
#define DEFAULT_SUFFIX   "ppm"
static char *
find_fname( const char *prefix, const char *suffix )
{
  struct stat s;
  unsigned i = 1;
  char *n = NULL;
  do
    {
      if ( n != NULL )
	free( n );

      asprintf( &n, "%s-%05d.%s", prefix, i++, suffix );
    }
  while ( stat( n, &s ) == 0 );
  return n;
}


static int
ppm_saveimage( const char *filename )
{
  if ( screen == NULL )
    return -1;

  FILE *fd = fopen( filename, "w" );
  if ( fd == NULL )
    {
      perror( "Could not open file" );
      return -1;
    }

  uint16_t w = screen->size.x;
  uint16_t h = screen->size.y;

  fprintf( fd, "P6\n%d %d\n255\n", w, h );

  color_t *p, *end = screen->buffer + ( w * h );
  for ( p=screen->buffer; p < end; p++ )
    fprintf( fd, "%c%c%c", p->r, p->g, p->b );

  fclose( fd );
  return 0;
}


/**
 * Change visual layer to use PPM.
 */
void
visual_change_layer_ppm( void )
{
  visual_change_layer( &ppm_layer_ops );
}


/**
 * Quit engine.
 */
void
visual_ppm_quit( void )
{
  if ( screen != NULL )
    surface_free( screen );
}

/**
 * Initialize video device using the given dimensions.
 *
 * It ouput video information using info(), see debug.h to set the right
 * debug_level to see this info.
 *
 * @param w screen width
 * @param h screen height
 *
 * @return 0 on succes, error otherwise.
 */
int
visual_ppm_init( uint16_t w, uint16_t h )
{
  screen = surface_new( w, h );
  return screen == NULL;
}

/**
 * Update screen.
 *
 * @return 0 on success, error otherwise.
 */
int
visual_ppm_update( void )
{
  if ( screen == NULL )
    return -1;

  char *f = find_fname( DEFAULT_FILENAME, DEFAULT_SUFFIX );
  int r;
  if ( ( r = ppm_saveimage( f ) ) == 0 )
    printf( "Image saved to: \"%s\"\n", f );
  free( f );
  return r;
}

/**
 * Wait for key and return key symbol as described in SDLKey(3).
 *
 * It also handle program exit (SDL_QUIT) and do screen refreshes when
 * required (SDL_ACTIVEEVENT).
 *
 * @return key symbol or 0 on error.
 */
int
visual_ppm_waitkey( void )
{
  return 0;
}


/**
 * Clear (blanks) screen.
 *
 * @return 0 on success, error otherwise.
 */
int
visual_ppm_clear( void )
{
  if ( screen == NULL )
    return -1;

  surface_clear( screen );
  return 0;
}


/**
 * Fill rectangular screen region with the given color.
 *
 * @param color what color to paint.
 * @param rectan
 *
 * @return 0 on success, error otherwise.
 */
int
visual_ppm_fill( color_t color, rect_t rect )
{
  if ( screen == NULL )
    return -1;

  surface_fill( screen, color, rect );
  return 0;
}


/**
 * Display the surface at the given position
 *
 * @param surface what to display
 * @param pos where to display
 *
 * @return 0 on success, error otherwise.
 */
int
visual_ppm_display( surface_t *surface, coord_t pos )
{
  if ( screen == NULL )
    return -1;

  /* Check if it's outside screen */
  if ( ( pos.x > screen->size.x ) || ( pos.y > screen->size.y ) )
    return -1;

  /* Limit drawing to inside screen */
  uint16_t x_max = min( surface->size.x, (uint16_t)screen->size.x );
  uint16_t y_max = min( surface->size.y, (uint16_t)screen->size.y );

  /* Ensure SDL surface is same depth as our color_t */
  uint_fast8_t bpp = sizeof(color_t);

  /* setup pointers */
  color_t *src = &(PIXEL_AT( surface, 0, 0 ));
  color_t *dst = &(PIXEL_AT( screen, pos.x, pos.y ));

  uint16_t dy = y_max - pos.y;
  uint16_t dx = x_max - pos.x;
  uint16_t dx_bytes = dx * bpp;

  /* copy src to dst */
  uint_fast16_t i;
  for ( i=0; i < dy; i++ )
    {
      memcpy( dst, src, dx_bytes ); /* this is fast! */
      /* go to next line */
      src = src + surface->size.x;
      dst = dst + screen->size.x;
    }
  return 0;
}


/**
 * Save the display to a bitmap image.
 *
 * @param filename where to save the file.
 *
 * @return 0 on success, error otherwise.
 */
int
visual_ppm_save( const char *filename )
{
  return ppm_saveimage( filename );
}
